/*
 * Copyright 2013 Krzysztof Smigielski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.nilcaream.dirstore.cli.command;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.base.Throwables;
import com.nilcaream.dirstore.cli.Configuration;
import com.nilcaream.dirstore.core.common.ArchiveCreator;
import com.nilcaream.dirstore.core.common.ProgressMeter;
import com.nilcaream.dirstore.core.model.Archive;
import com.nilcaream.dirstore.core.model.ArchiveElement;
import org.slf4j.Logger;

import javax.inject.Inject;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.nilcaream.dirstore.core.common.Utils.toReadableFileSize;
import static java.lang.String.format;

/**
 * TODO
 * <p/>
 * Krzysztof Smigielski 4/20/13 8:33 PM
 */
public abstract class AbstractTrimCommand implements Command {

    @Inject
    protected Logger logger;

    @Inject
    protected Configuration configuration;

    @Inject
    protected ProgressMeter progressMeter;

    public abstract ArchiveCreator getTrimmer();

    protected abstract void doTrim(Archive archive, OutputStream outputStream) throws IOException;

    public void trim(Archive archive) throws IOException {
        Stopwatch stopwatch = new Stopwatch();
        Path root = Paths.get(configuration.getInputName());
        logger.info("trimming " + root.toAbsolutePath().normalize());

        String outputName = createOutputFileName(configuration.getOutputName());
        FileOutputStream outputStream = createOutputStream(outputName);

        stopwatch.start();
        doTrim(archive, outputStream);
        stopwatch.stop();

        File result = new File(outputName);
        logger.info(format("trimmed input has been stored as %s of %s size in %dms", result.getAbsolutePath(), toReadableFileSize(result.length()), stopwatch.elapsed(TimeUnit.MILLISECONDS)));
    }

    @Override
    public boolean isValid() {
        boolean isValid = false;
        if (configuration.isCreateArchive()) {
            if (configuration.isQuickMode()) {
                logger.debug("quick mode: -j and -b flags are ignored");
            }
            if (isNullOrEmpty(configuration.getInputName()) || isNullOrEmpty(configuration.getOutputName())) {
                logger.warn("input name or output name was not provided.");
            } else {
                isValid = true;
            }
        }
        return isValid;
    }

    @Override
    public void execute() {
        Archive archive = buildArchive();
        ScheduledExecutorService executorService = null;

        try {
            if (configuration.getProgressBarRefreshRate() > 0) {
                executorService = startProgressLogging(archive);
            }
            trim(archive);
        } catch (IOException e) {
            throw Throwables.propagate(e);
        } finally {
            if (executorService != null) {
                executorService.shutdown();
            }
        }
    }

    protected FileOutputStream createOutputStream(String fileName) throws FileNotFoundException {
        Preconditions.checkArgument(!new File(fileName).exists(), fileName + " exists");
        return new FileOutputStream(fileName);
    }

    private Archive buildArchive() {
        Stopwatch stopwatch = new Stopwatch();
        Path root = Paths.get(configuration.getInputName());
        logger.info("scanning " + root.toAbsolutePath().normalize());
        Archive archive;
        try {
            stopwatch.start();
            archive = getTrimmer().createArchive(root);
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
        String totalSize = toReadableFileSize(calculateTotalSize(archive.getElements()));
        long elapsedInMs = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        logger.info(format("found %d elements of %s size in %dms ", archive.getElements().size(), totalSize, elapsedInMs));
        return archive;
    }

    private long calculateTotalSize(Collection<ArchiveElement> archiveElements) {
        long size = 0;
        for (ArchiveElement archiveElement : archiveElements) {
            size += archiveElement.getOriginalSize();
        }
        return size;
    }

    protected String createOutputFileName(String archiveName) {
        return archiveName + ".dst.zip";
    }

    private ScheduledExecutorService startProgressLogging(final Archive archive) {
        final List<ArchiveElement> elements = archive.getElements();
        final String totalSize = toReadableFileSize(calculateTotalSize(elements));

        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(new Runnable() {
            private String lastMessage = "";

            @Override
            public void run() {
                int elementsProcessed = progressMeter.getCurrent(archive);
                String processedElementsSize = toReadableFileSize(calculateCurrentSize(elements, elementsProcessed));
                String message = format("processed %d of %d elements (%s of %s)", elementsProcessed, elements.size(), processedElementsSize, totalSize);
                if (!message.equals(lastMessage)) {
                    logger.info(message);
                    lastMessage = message;
                }
            }
        }, 1, configuration.getProgressBarRefreshRate(), TimeUnit.SECONDS);
        return executorService;
    }

    private long calculateCurrentSize(List<ArchiveElement> archiveElements, int numberOfProcessed) {
        long size = 0;
        for (int index = 0; index < numberOfProcessed; index++) {
            ArchiveElement archiveElement = archiveElements.get(index);
            size += archiveElement.getOriginalSize();
        }
        return size;
    }
}
